import {COMPOUNDING_WITHDRAWAL_PREFIX, GENESIS_SLOT, MIN_ACTIVATION_BALANCE} from "@lodestar/params";
import {ValidatorIndex, ssz} from "@lodestar/types";
import {G2_POINT_AT_INFINITY} from "../constants/constants.js";
import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
import {hasEth1WithdrawalCredential} from "./capella.js";
import {hasBuilderWithdrawalCredential} from "./gloas.ts";

export function hasCompoundingWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean {
  return (
    withdrawalCredentials[0] === COMPOUNDING_WITHDRAWAL_PREFIX || hasBuilderWithdrawalCredential(withdrawalCredentials)
  );
}

export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean {
  return (
    hasCompoundingWithdrawalCredential(withdrawalCredentials) || hasEth1WithdrawalCredential(withdrawalCredentials)
  );
}

export function switchToCompoundingValidator(
  state: CachedBeaconStateElectra | CachedBeaconStateGloas,
  index: ValidatorIndex
): void {
  const validator = state.validators.get(index);

  // directly modifying the byte leads to ssz missing the modification resulting into
  // wrong root compute, although slicing can be avoided but anyway this is not going
  // to be a hot path so its better to clean slice and avoid side effects
  const newWithdrawalCredentials = Uint8Array.prototype.slice.call(
    validator.withdrawalCredentials,
    0,
    validator.withdrawalCredentials.length
  );
  newWithdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX;
  validator.withdrawalCredentials = newWithdrawalCredentials;
  queueExcessActiveBalance(state, index);
}

export function queueExcessActiveBalance(
  state: CachedBeaconStateElectra | CachedBeaconStateGloas,
  index: ValidatorIndex
): void {
  const balance = state.balances.get(index);
  if (balance > MIN_ACTIVATION_BALANCE) {
    const validator = state.validators.getReadonly(index);
    const excessBalance = balance - MIN_ACTIVATION_BALANCE;
    state.balances.set(index, MIN_ACTIVATION_BALANCE);

    const pendingDeposit = ssz.electra.PendingDeposit.toViewDU({
      pubkey: validator.pubkey,
      withdrawalCredentials: validator.withdrawalCredentials,
      amount: excessBalance,
      // Use bls.G2_POINT_AT_INFINITY as a signature field placeholder
      signature: G2_POINT_AT_INFINITY,
      // Use GENESIS_SLOT to distinguish from a pending deposit request
      slot: GENESIS_SLOT,
    });
    state.pendingDeposits.push(pendingDeposit);
  }
}

/**
 * Since we share pubkey2index, pubkey maybe added by other epoch transition but we don't have that validator in this state
 */
export function isPubkeyKnown(state: CachedBeaconStateElectra | CachedBeaconStateGloas, pubkey: Uint8Array): boolean {
  return isValidatorKnown(state, state.epochCtx.getValidatorIndex(pubkey));
}

/**
 * Since we share pubkey2index, validatorIndex maybe not null but we don't have that validator in this state
 */
export function isValidatorKnown(
  state: CachedBeaconStateElectra | CachedBeaconStateGloas,
  index: ValidatorIndex | null
): index is ValidatorIndex {
  return index !== null && index < state.validators.length;
}
